﻿#include "Lisp.h"

#include "Readline.h"
#include "Reader.h"
#include "Printer.h"
#include "Env.h"
#include "Util.h"
#include "StaticList.h"
#include "Exception.h"
#include "Core.h"

#include <sstream>

Lisp::ValPtr READ(const std::string &input);
Lisp::ValPtr EVAL(Lisp::ValPtr ast, Lisp::EnvPtr env);
std::string PRINT(Lisp::ValPtr val);
std::string Rep(const std::string &input, Lisp::EnvPtr env);
std::string SafeRep(const std::string &input, Lisp::EnvPtr env);

static void MakeArgv(Lisp::EnvPtr env, int argc, char *argv[]);
static bool IsTailPosition(Lisp::ValPtr body, Lisp::ValPtr currentExpr);

static Lisp::ValPtr Quasiquote(Lisp::ValPtr obj);

static void InstallLispFunctions(Lisp::EnvPtr env);

static Lisp::Readline s_readline;
static Lisp::EnvPtr s_repEnv(new Lisp::Env);
static std::vector<std::string> s_stack;

int main(int argc, char *argv[])
{
  Lisp::InitNativeBuiltIns(s_repEnv);
  InstallLispFunctions(s_repEnv);

  MakeArgv(s_repEnv, argc - 2, argv + 2);

  const std::string prompt = "user> ";
  std::string input;

  if (argc > 1)
  {
    std::string filename = Lisp::StringUtils::Escape(argv[1]);
    const std::string code = Lisp::StringUtils::Format("(load-file %s)", filename.c_str());
    std::string out = SafeRep(code, s_repEnv);
    std::cout << out << std::endl;
    return 0;
  }

  while (s_readline.Get(prompt, input))
  {
    std::string out = SafeRep(input, s_repEnv);
    if (out.length() > 0)
      std::cout << out << std::endl;
  }
  return 0;
}

Lisp::ValPtr READ(const std::string &input)
{
  return Lisp::ReadStr(input);
}

std::string EvalSymbol(Lisp::ValPtr symbolName, Lisp::EnvPtr env)
{
  Lisp::SymbolType *sym = DYNAMIC_CAST(Lisp::SymbolType, symbolName);
  if (!sym)
  {
    Lisp::ListType *apply = DYNAMIC_CAST(Lisp::ListType, symbolName);
    Lisp::ValPtr res = EVAL(symbolName, env);
    sym = DYNAMIC_CAST(Lisp::SymbolType, res);
    if (!sym)
    {
      throw Lisp::RuntimeException("EvalSymbol: not a symbol: " + symbolName->ToString());
    }
  }
  return sym->Value();
}

Lisp::ValPtr EVAL(Lisp::ValPtr ast, Lisp::EnvPtr env)
{
  if (!env)
    env = s_repEnv;

  while (1)
  {
    const Lisp::EnvPtr dbgenv = env->Find("DEBUG-EVAL");
    if (dbgenv && dbgenv->Get("DEBUG-EVAL")->IsTrue())
    {
      std::cout << "EVAL: " << PRINT(ast) << "\n";
    }

    Lisp::ListType *list = DYNAMIC_CAST(Lisp::ListType, ast);
    if (!list || (list->Size() == 0))
    {
      return ast->Eval(env);
    }

    // !: 会改变当前环境
    // *: 会创建子环境

    if (const Lisp::SymbolType *symbol = DYNAMIC_CAST(Lisp::SymbolType, list->At(0)))
    {
      const std::string special = symbol->Value();
      const size_t argCount = list->Size() - 1;

      if (special == "def!")
      {
        Lisp::CheckArgsIs("def!", argCount, 2);
        const std::string symbolName = EvalSymbol(list->At(1), env);
        return env->Set(symbolName, EVAL(list->At(2), env));
      }
      else if (special == "defmacro!")
      {
        Lisp::CheckArgsIs("defmacro!", argCount, 2);
        const Lisp::SymbolType *sym = VALUE_CAST(Lisp::SymbolType, list->At(1));
        Lisp::ValPtr body = EVAL(list->At(2), env);
        const Lisp::LambdaType *lambda = VALUE_CAST(Lisp::LambdaType, body);
        return env->Set(sym->Value(), Lisp::MacroVal(*lambda));
      }
      else if (special == "let*")
      {
        Lisp::CheckArgsIs("let*", argCount, 2);
        const Lisp::SequenceType *bindings = VALUE_CAST(Lisp::SequenceType, list->At(1));
        const size_t count = Lisp::CheckArgsEven("let*", bindings->Size());
        Lisp::EnvPtr innerEnv(new Lisp::Env(env));
        for (size_t i = 0; i < count; i += 2)
        {
          const Lisp::SymbolType *sym = VALUE_CAST(Lisp::SymbolType, bindings->At(i));
          innerEnv->Set(sym->Value(), EVAL(bindings->At(i + 1), innerEnv));
        }
        ast = list->At(2);
        env = innerEnv;
        continue; // TCO
      }
      else if (special == "return")
      {
        Lisp::CheckArgsBetween("return", argCount, 0, 1);
        Lisp::ValPtr returnValue = (argCount > 0) ? EVAL(list->At(1), env) : Lisp::NilVal();
        throw Lisp::ReturnException(returnValue);
      }
      else if (special == "break")
      {
        Lisp::CheckArgsIs("break", argCount, 0);
        throw Lisp::BreakException();
      }
      else if (special == "continue")
      {
        Lisp::CheckArgsIs("continue", argCount, 0);
        throw Lisp::ContinueException();
      }
      else if (special == "while")
      {
        Lisp::CheckArgsIs("while", argCount, 2);
        Lisp::ValPtr test = list->At(1);
        Lisp::ValPtr body = list->At(2);

        while (EVAL(test, env)->IsTrue())
        {
          try
          {
            EVAL(body, env);
          }
          catch (Lisp::BreakException &e)
          {
            break;
          }
          catch (Lisp::ContinueException &e)
          {
            continue;
          }
        }

        return Lisp::NilVal();
      }
      else if (special == "do")
      {
        Lisp::CheckArgsAtLeast("do", argCount, 1);

        Lisp::ValPtr returnType;

        for (int i = 1; i < argCount; i++)
        {
          Lisp::ValPtr line = list->At(i);
          EVAL(line, env);
        }

        ast = list->At(argCount);
        continue; // TCO
      }
      else if (special == "if")
      {
        Lisp::CheckArgsBetween("if", argCount, 2, 3);
        bool isTrue = EVAL(list->At(1), env)->IsTrue();
        if (!isTrue && argCount == 2)
        {
          return Lisp::NilVal();
        }
        ast = list->At(isTrue ? 2 : 3);
        continue; // TCO
      }
      else if (special == "fn*")
      {
        Lisp::CheckArgsIs("fn*", argCount, 2);
        const Lisp::SequenceType *params = VALUE_CAST(Lisp::SequenceType, list->At(1));
        std::vector<std::string> paramNames;
        for (size_t i = 0; i < params->Size(); ++i)
        {
          const Lisp::SymbolType *sym = VALUE_CAST(Lisp::SymbolType, params->At(i));
          paramNames.push_back(sym->Value());
        }
        return Lisp::LambdaVal(paramNames, list->At(2), env);
      }
      else if (special == "quasiquote")
      {
        // 标记引用，不应该被求值
        Lisp::CheckArgsIs("quasiquote", argCount, 1);
        ast = Quasiquote(list->At(1));
        continue; // TCO
      }
      else if (special == "quote")
      {
        // 标记引用，不应该被求值
        Lisp::CheckArgsIs("quote", argCount, 1);
        return list->At(1);
      }
      else if (special == "try*")
      {
        Lisp::ValPtr tryBody = list->At(1);

        if (argCount == 1)
        {
          ast = tryBody;
          continue;
        }

        Lisp::CheckArgsIs("try*", argCount, 2);
        const Lisp::ListType *catchBlock = VALUE_CAST(Lisp::ListType, list->At(2));

        Lisp::CheckArgsIs("catch*", catchBlock->Size() - 1, 2);
        LISP_CHECK(VALUE_CAST(Lisp::SymbolType, catchBlock->At(0))->Value() == "catch*",
                   "Catch block must begin with catch*");

        const Lisp::SymbolType *excSym = VALUE_CAST(Lisp::SymbolType, catchBlock->At(1));
        Lisp::ValPtr excVal;
        try
        {
          return EVAL(tryBody, env);
        }
        catch (std::string &s)
        {
          excVal = Lisp::StringVal(s);
        }
        catch (Lisp::EmptyInputException &e)
        {
          ast = Lisp::NilVal();
        }
        catch (Lisp::ReturnException &e)
        {
          ast = e.GetValue();
        }
        catch (Lisp::Exception &e)
        {
          excVal = Lisp::StringVal(e.What());
        }
        catch (Lisp::ValPtr &e)
        {
          excVal = e;
        }

        if (excVal)
        {
          // 获取到了异常
          env = Lisp::EnvPtr(new Lisp::Env(env));
          env->Set(excSym->Value(), excVal);
          ast = catchBlock->At(2);
        }
        continue; // TCO
      }
    }

    Lisp::ValPtr op = EVAL(list->At(0), env);
    if (const Lisp::LambdaType *lambda = DYNAMIC_CAST(Lisp::LambdaType, op))
    {
      if (lambda->IsMacro())
      {
        ast = lambda->Apply(new Lisp::ListType(list->begin() + 1, list->end())); // Apply(list->begin() + 1, list->end());
        continue;
      }

      Lisp::ValVec *items = STATIC_CAST(Lisp::ListType, list->Rest())->EvalItems(env);

      // 检查当前表达式是否是函数体的最后一个表达式
      Lisp::ValPtr body = lambda->GetBody();
      const bool isTail = IsTailPosition(body, list);

      if (isTail)
      {
        // TCO
        ast = lambda->GetBody();
        env = lambda->MakeEnv(new Lisp::ListType(items->begin(), items->end()));
        Lisp::ListPtr args(new Lisp::ListType(items->begin(), items->end()));
        continue;
      }
      else
      {
        Lisp::ValVec *items = STATIC_CAST(Lisp::ListType, list->Rest())->EvalItems(env);
        Lisp::ListPtr args(new Lisp::ListType(items->begin(), items->end()));
        Lisp::ValPtr val = lambda->Apply(args);
        return val;
      }
    }
    else
    {
      Lisp::ValVec *items = STATIC_CAST(Lisp::ListType, list->Rest())->EvalItems(env);
      Lisp::ValPtr val = APPLY(op, new Lisp::ListType(items->begin(), items->end()));
      return val;
    }
  }
}

Lisp::ValPtr APPLY(Lisp::ValPtr op, Lisp::ListPtr args)
{
  const Lisp::ApplicableType *handler = DYNAMIC_CAST(Lisp::ApplicableType, op);
  LISP_CHECK(handler != NULL,
             "\"%s\" is not applicable", op->ToString(true).c_str());

  Lisp::ValPtr val = handler->Apply(args);
  return val;
}

std::string PRINT(Lisp::ValPtr val)
{
  return Lisp::PrintStr(val);
}

std::string Rep(const std::string &input, Lisp::EnvPtr env)
{
  std::string res = PRINT(EVAL(READ(input), env));
  return res;
}

std::string SafeRep(const std::string &input, Lisp::EnvPtr env)
{
  try
  {
    return Rep(input, s_repEnv);
  }
  catch (const Lisp::EmptyInputException &e)
  {
    return "";
  }
  catch (const Lisp::RuntimeException &e)
  {
    std::stringstream ss;
    for (auto it = s_stack.rbegin(); it != s_stack.rend(); it++)
    {
      ss << " in " << *it << std::endl;
    }
    return "Runtime Error: " + e.What();
  }
  catch (const Lisp::ReturnException &e)
  {
    return PRINT(e.GetValue());
  }
  catch (const std::exception &e)
  {
    return "Runtime Error: " + std::string(e.what());
  }
  catch (const Lisp::ValPtr &e)
  {
    return "Runtime Error: " + std::string(e->ToString());
  }
}

static bool IsSymbol(Lisp::ValPtr obj, const std::string &text)
{
  const Lisp::SymbolType *symbol = DYNAMIC_CAST(Lisp::SymbolType, obj);
  return symbol && (symbol->Value() == text);
}

static Lisp::ValPtr StartWith(const Lisp::ValPtr obj, const std::string &sym)
{
  const Lisp::ListType *list = DYNAMIC_CAST(Lisp::ListType, obj);
  if (!list || list->IsEmpty() || !IsSymbol(list->At(0), sym))
    return nullptr;
  Lisp::CheckArgsIs(sym, list->Size() - 1, 1);
  return list->At(1);
}

static Lisp::ValPtr Quasiquote(Lisp::ValPtr obj)
{
  if (DYNAMIC_CAST(Lisp::SymbolType, obj) || DYNAMIC_CAST(Lisp::HashType, obj))
    return Lisp::ListVal({Lisp::SymbolVal("quote"), obj});

  const Lisp::SequenceType *seq = DYNAMIC_CAST(Lisp::SequenceType, obj);
  if (!seq)
    return obj;

  const Lisp::ValPtr unquoted = StartWith(obj, "unquote");
  if (unquoted)
    return unquoted;

  Lisp::ValPtr res = Lisp::ListVal(new Lisp::ValVec(0));
  for (int i = seq->Size() - 1; i >= 0; --i)
  {
    const Lisp::ValPtr elt = seq->At(i);
    const Lisp::ValPtr spliceUnquote = StartWith(elt, "splice-unquote");
    if (spliceUnquote)
      res = Lisp::ListVal({Lisp::SymbolVal("concat"), spliceUnquote, res});
    else
      res = Lisp::ListVal({Lisp::SymbolVal("cons"), Quasiquote(elt), res});
  }

  return res;
}

static void MakeArgv(Lisp::EnvPtr env, int argc, char *argv[])
{
  Lisp::ValVec *args = new Lisp::ValVec();
  for (int i = 0; i < argc; i++)
  {
    args->push_back(Lisp::StringVal(argv[i]));
  }
  env->Set("*ARGV*", Lisp::ListVal(args));
}

static bool IsTailPosition(Lisp::ValPtr body, Lisp::ValPtr currentExpr)
{
  // 对于序列类型，检查是否为最后一个元素
  if (const Lisp::SequenceType *seq = DYNAMIC_CAST(Lisp::SequenceType, body))
  {
    if (seq->Size() == 0)
    {
      return false;
    }
    // 获取最后一个元素
    Lisp::ValPtr lastExpr = seq->At(seq->Size() - 1);

    // 比较当前表达式和最后一个表达式
    return currentExpr == lastExpr;
  }
  else
  {
    return body == currentExpr;
  }
}

static const char *lispFunctionTable[] = {
    "(def! *host-language* \"C++\")",
    "(def! load-file (fn* (filename) \
        (eval (read-string (str \"(do \" (slurp filename) \"nil)\")))))",
    "(load-file \"../BuiltIn/LispBuiltIn.lisp\")"};

static void InstallLispFunctions(Lisp::EnvPtr env)
{
  for (auto &function : lispFunctionTable)
  {
    Rep(function, env);
  }
}

Lisp::ValPtr Readline(const std::string &prompt)
{
  std::string input;
  if (s_readline.Get(prompt, input))
    return Lisp::StringVal(input);
  return Lisp::NilVal();
}